﻿using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Ports;
using System.Threading;

namespace ModbusRTULib
{
    public class ModbusRTU
    {
        #region 属性定义
        private readonly SerialPort serialPort;
        /// <summary>
        /// 读取超时时间
        /// </summary>
        public int ReadTimeout { get; set; } = 2000;
        /// <summary>
        /// 写入超时时间
        /// </summary>
        public int WriteTimeout { get; set; } = 2000;
        /// <summary>
        /// 每次串口通信的延迟时间
        /// </summary>
        public int SleepTime = 10;
        /// <summary>
        /// 最大读取的超时时间
        /// </summary>
        public int ReceiveTimeout { get; set; } = 5000;
        public bool dtrEnable = false;
        public bool rtsEnable = false;
        public bool DTREnable
        {
            get { return dtrEnable; }
            set
            {
                dtrEnable = value;
                this.serialPort.DtrEnable = value;
            }
        }
        public bool RtsEnable
        {
            get { return rtsEnable; }
            set
            {
                rtsEnable = value;
                this.serialPort.RtsEnable = value;
            }
        }
        #endregion

        public ModbusRTU()
        {
            serialPort = new SerialPort();
        }

        #region 建立连接和关闭连接
        /// <summary>
        /// 建立连接
        /// </summary>
        /// <param name="portName">串口号</param>
        /// <param name="baudRate">波特率</param>
        /// <param name="parity">校验位</param>
        /// <param name="dataBits">数据位</param>
        /// <param name="stopBits">停止位</param>
        /// <returns></returns>
        public bool Connect(string portName, int baudRate = 9600, Parity parity = Parity.None, int dataBits = 8, StopBits stopBits = StopBits.One)
        {
            if (serialPort != null && serialPort.IsOpen)
            {
                serialPort.Close();
            }
            serialPort.PortName = portName;
            serialPort.BaudRate = baudRate;
            serialPort.Parity = parity;
            serialPort.DataBits = dataBits;
            serialPort.StopBits = stopBits;
            serialPort.ReadTimeout = this.ReadTimeout;
            serialPort.WriteTimeout = this.WriteTimeout;
            try
            {
                this.serialPort.Open();
            }
            catch (Exception)
            {
                return false;
            }
            return true;
        }

        public void DisConnect()
        {
            if (serialPort != null && serialPort.IsOpen)
            {
                this.serialPort.Close();
            }
        }
        #endregion

        #region 01H读取输出线圈
        /// <summary>
        /// 01H读取输出线圈
        /// </summary>
        /// <param name="slaveld">站地址</param>
        /// <param name="start">起始地址</param>
        /// <param name="length"></param>
        /// <returns></returns>
        public byte[] ReadOutputCoils(byte slaveId, ushort start, ushort length)
        {
            // 第一步：拼接报文
            List<byte> SendCommand = new List<byte>();
            // 从站地址
            SendCommand.Add(slaveId);
            // 功能码
            SendCommand.Add(0x01);
            // 开始线圈地址
            SendCommand.Add((byte)(start / 256));
            SendCommand.Add((byte)(start % 256));
            // 线圈数量
            SendCommand.Add((byte)(length / 256));
            SendCommand.Add((byte)(length % 256));
            // crc
            byte[] crc = Utils.CalculateCRC16(SendCommand.ToArray());
            SendCommand.AddRange(crc);

            // 第二步：发送报文
            // 第三步：接收报文
            byte[] receive = null;
            bool res = SendAndRevice(SendCommand.ToArray(), ref receive);
            if (!res)
            {
                return null;
            }
            // 第四步：验证报文
            res = Utils.CheckCrc(receive);
            if (!res)
            {
                return null;
            }
            int byteLength = length % 8 == 0 ? length : length / 8 + 1;
            if (receive.Length == 5 + byteLength)
            {
                if (receive[0] == slaveId && receive[1] == 0x01 && receive[2] == byteLength)
                {
                    // 第五步：解析报文
                    byte[] result = new byte[byteLength];
                    Array.Copy(receive, 3, result, 0, byteLength);
                    return result;
                }
            }
            return null;
        }
        #endregion

        #region 02H读取输入线圈
        // 把上面代码的0x01改成0x02
        #endregion

        #region 03H读取输出寄存器
        public byte[] ReadOutputRegisters(byte slaveId, ushort start, ushort length)
        {
            // 第一步：拼接报文
            List<byte> SendCommand = new List<byte>();
            SendCommand.Add(slaveId);
            SendCommand.Add(0x03);
            SendCommand.Add((byte)(start / 256));
            SendCommand.Add((byte)(start % 256));
            SendCommand.Add((byte)(length / 256));
            SendCommand.Add((byte)(length % 256));
            byte[] crc = Utils.CalculateCRC16(SendCommand.ToArray());
            SendCommand.AddRange(crc);

            // 第二步：发送报文
            // 第三步：接收报文
            byte[] receive = null;
            int byteLength = length * 2;
            bool res = SendAndRevice(SendCommand.ToArray(), ref receive);
            if (!res)
            {
                return null;
            }
            // 第四步：验证报文
            res = Utils.CheckCrc(receive);
            if (!res)
            {
                return null;
            }
            if (receive.Length != 5 + byteLength)
            {
                return null;
            }
            if (receive[0] == slaveId && receive[1] == 0x03 && receive[2] == byteLength)
            {
                // 第五步：解析报文
                byte[] result = new byte[byteLength];
                Array.Copy(receive, 3, result, 0, byteLength);
                return result;
            }
            return null;
        }
        #endregion

        #region 04H读取输入寄存器
        // 把上面代码的0x03改成0x04
        #endregion

        #region 05H写入单线圈
        public bool PreSetSingleCoil(byte slaveId, ushort start, bool value)
        {
            // 第一步：拼接报文
            List<byte> SendCommand = new List<byte>();
            SendCommand.Add(slaveId);
            SendCommand.Add(0x05);
            SendCommand.Add((byte)(start / 256));
            SendCommand.Add((byte)(start % 256));
            SendCommand.Add((byte)(value ? 0xFF : 0x00));
            SendCommand.Add(0x00);
            byte[] crc = Utils.CalculateCRC16(SendCommand.ToArray());
            SendCommand.AddRange(crc);
            // 第二步：发送报文
            // 第三步：接收报文
            byte[] receive = null;
            bool res = SendAndRevice(SendCommand.ToArray(), ref receive);
            if (!res)
            {
                return false;
            }
            // 第四步：验证报文
            res = Utils.CheckCrc(receive);
            if (!res)
            {
                return false;
            }
            if (receive.Length != 8)
            {
                return false;
            }
            if (receive[0] != slaveId || receive[1] != 0x03)
            {
                return false;
            }
            return Array.Equals(SendCommand.ToArray(), receive);
        }
        #endregion

        #region 06H写入单寄存器
        public bool PreSetSingleRegister(byte slaveId, ushort start, byte[] value)
        {
            // 第一步：拼接报文
            List<byte> SendCommand = new List<byte>();
            SendCommand.Add(slaveId);
            SendCommand.Add(0x06);
            SendCommand.Add((byte)(start / 256));
            SendCommand.Add((byte)(start % 256));
            SendCommand.AddRange(value);
            SendCommand.Add(0x00);
            byte[] crc = Utils.CalculateCRC16(SendCommand.ToArray());
            SendCommand.AddRange(crc);
            // 第二步：发送报文
            // 第三步：接收报文
            byte[] receive = null;
            bool res = SendAndRevice(SendCommand.ToArray(), ref receive);
            if (!res)
            {
                return false;
            }
            // 第四步：验证报文
            res = Utils.CheckCrc(receive);
            if (!res || receive.Length != 8 || receive[0] != slaveId || receive[1] != 0x03)
            {
                return false;
            }
            return Array.Equals(SendCommand.ToArray(), receive);
        }

        #endregion

        #region 0FH写入多线程
        public bool PreSetMultiCoils(byte slaveId, ushort start, bool[] value)
        {
            // 第一步：拼接报文
            List<byte> SendCommand = new List<byte>();
            SendCommand.Add(slaveId);
            SendCommand.Add(0x0F);
            SendCommand.Add((byte)(start / 256));
            SendCommand.Add((byte)(start % 256));
            SendCommand.Add((byte)(value.Length / 256));
            SendCommand.Add((byte)(value.Length % 256));
            SendCommand.AddRange(Utils.GetByteArrayFromBoolArray(value));
            byte[] crc = Utils.CalculateCRC16(SendCommand.ToArray());
            SendCommand.AddRange(crc);
            // 第二步：发送报文
            // 第三步：接收报文
            byte[] receive = null;
            bool sendSuccess = SendAndRevice(SendCommand.ToArray(), ref receive);
            if (!sendSuccess)
            {
                return false;
            }
            // 第四步：验证报文
            bool checkCrc = Utils.CheckCrc(receive);
            if (!checkCrc || receive.Length != 8)
            {
                return false;
            }
            return Array.Equals(SendCommand.ToArray(), receive);
        }
        #endregion

        #region 0FH写入多寄存器
        public bool PreSetMultiRegisters(byte slaveId, ushort start, byte[] values)
        {
            if (values == null || values.Length == 0 || values.Length % 2 == 1)
            {
                return false;
            }
            // 第一步：拼接报文
            List<byte> SendCommand = new List<byte>();
            int registerLength = values.Length / 2;
            SendCommand.Add(slaveId);
            SendCommand.Add(0x10);
            SendCommand.Add((byte)(start / 256));
            SendCommand.Add((byte)(start % 256));
            SendCommand.Add((byte)(registerLength / 256));
            SendCommand.Add((byte)(registerLength % 256));
            SendCommand.Add((byte)values.Length);
            SendCommand.AddRange(values);
            SendCommand.AddRange(Utils.CalculateCRC16(SendCommand.ToArray()));
            // 第二步：发送报文
            // 第三步：接收报文
            byte[] receive = null;
            bool sendSuccess = SendAndRevice(SendCommand.ToArray(), ref receive);
            if (!sendSuccess)
            {
                return false;
            }
            // 第四步：验证报文
            bool crcCheck = Utils.CheckCrc(receive);
            if (!crcCheck || receive.Length != 8)
            {
                return false;
            }
            return Array.Equals(SendCommand.ToArray(), receive);
        }
        #endregion

        /// <summary>
        /// 发送并接收串口
        /// </summary>
        /// <param name="send"></param>
        /// <param name="receive"></param>
        /// <returns>判断是否成功</returns>
        private bool SendAndRevice(byte[] send, ref byte[] receive)
        {
            try
            {
                this.serialPort.Write(send, 0, send.Length);
                byte[] buffer = new byte[1024];
                MemoryStream stream = new MemoryStream();
                DateTime start = DateTime.Now;
                while (true)
                {
                    Thread.Sleep(SleepTime);
                    if (this.serialPort.BytesToRead > 0)
                    {
                        int count = this.serialPort.Read(buffer, 0, buffer.Length);
                        stream.Write(buffer, 0, count);
                    }
                    else
                    {
                        if (stream.Length > 0)
                        {
                            break;
                        }
                        else if ((DateTime.Now - start).TotalMilliseconds >= ReceiveTimeout)
                        {
                            return false;
                        }
                    }
                }
                receive = stream.ToArray();
                return true;
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
                return false;
            }
        }
    }
}
